国际化:unplugin-vue-i18n 集成
为什么需要 unplugin-vue-i18n
手动在 main.ts 中配置 vue-i18n 存在诸多问题:
- 需要手动配置目录、文件解析、消息导入
- 无法利用预编译优化性能
- 没有全局 API 的自动导入
unplugin-vue-i18n 是官方推荐的 Vite/webpack 插件,它提供:
- 预编译(Pre-compilation):在构建时将翻译资源编译为消息函数,提升运行时性能
- SFC 集成:支持在
<i18n>自定义块中定义翻译 - 自动导入:
useI18n等 API 自动可用
从 vue-i18n 9.3 版本起,默认启用 JIT 编译(Just-In-Time Compilation)。
安装
pnpm install -D @intlify/unplugin-vue-i18n
bash
Vite 配置
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueI18nPlugin from '@intlify/unplugin-vue-i18n'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
export default defineConfig({
plugins: [
vue(),
VueI18nPlugin({
// 指定翻译文件目录(必须配置)
include: resolve(
dirname(fileURLToPath(import.meta.url)),
'./locales/**'
),
// JIT 编译(9.3+ 默认开启)
jit: true,
// 仅打包运行时(默认 true,移除 Legacy API)
runtimeOnly: true,
// 全量安装 API(默认 true,包含 $t 等全局方法)
fullInstall: true,
}),
],
})
typescript
关键配置项说明
| 选项 | 默认值 | 说明 |
|---|---|---|
include | 无(必须配置) | 翻译文件的 glob 路径 |
jit | true(9.3+) | 启用 JIT 编译 |
runtimeOnly | true | 仅包含运行时,移除 Legacy API |
fullInstall | true | 安装所有 vue-i18n API |
支持的文件格式
| 格式 | 预编译 | 动态消息 | 备注 |
|---|---|---|---|
.json | 支持 | 不支持 | 最常用,推荐 |
.json5 | 支持 | 不支持 | 支持注释 |
.yaml / .yml | 支持 | 不支持 | 不支持 ` |
.js / .ts | 支持 | 支持(需 allowDynamic: true) | 需放在 source 目录外 |
注意:不建议将 locales 目录层级设置过深,会影响解析性能。
创建翻译文件
项目根目录/
├── locales/
│ ├── zh-CN.js # 中文翻译
│ └── en.js # 英文翻译
└── src/
text
// locales/zh-CN.js
export default {
hello: '你好,我是 mock',
}
javascript
// locales/en.js
export default {
hello: 'Hello, I am mock',
}
javascript
文件名遵循 IETF 语言标签标准(如
zh-CN、en、ja、ko)。
创建 i18n 模块
参考 VueUse 等开源项目的实践,创建独立的 i18n 模块文件:
// src/modules/i18n.ts
import { nextTick } from 'vue'
import { createI18n, type Locale } from 'vue-i18n'
// 支持的语言列表
export const availableLocales = ['zh-CN', 'en']
// 创建 i18n 实例
export const i18n = createI18n({
legacy: false,
locale: '',
messages: {},
})
// 已加载的语言缓存
const loadedLanguages: string[] = []
// 使用 import.meta.glob 动态导入翻译文件
const localesMap = Object.fromEntries(
Object.entries(
import.meta.glob('../../locales/*.js', { eager: true })
).map(([path, loadLocale]) => {
// 从路径中提取文件名作为语言标识
const localeKey = path.match(/\/([^/]+)\.js$/)?.[1] || ''
return [localeKey, loadLocale]
})
) as Record<string, { default: Record<string, string> }>
/**
* 设置 i18n 语言
*/
function setI18nLanguage(locale: string) {
// Composition API 模式下 locale 是 ref
;(i18n.global.locale as any).value = locale
// 设置 HTML 标签的 lang 属性
if (typeof document !== 'undefined') {
document.querySelector('html')?.setAttribute('lang', locale)
}
}
/**
* 懒加载语言包
*/
export async function loadLocaleMessages(locale: string) {
// 已加载则直接切换
if (loadedLanguages.includes(locale)) {
setI18nLanguage(locale)
return
}
// 从 localesMap 中加载对应的翻译文件
const messages = await localesMap[locale]
if (messages) {
i18n.global.setLocaleMessage(locale, messages.default)
loadedLanguages.push(locale)
setI18nLanguage(locale)
}
}
/**
* Vue 插件安装方法
*/
export function install(app: any) {
app.use(i18n)
// 加载默认语言
loadLocaleMessages('zh-CN')
}
export default install
typescript
在 main.ts 中注册
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './modules/i18n'
const app = createApp(App)
app.use(i18n)
app.mount('#app')
typescript
在组件中使用
<!-- src/pages/index.vue -->
<template>
<div>
<p>{{ t('hello') }}</p>
<button @click="changeLocale('zh-CN')">中文</button>
<button @click="changeLocale('en')">English</button>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { loadLocaleMessages } from '@/modules/i18n'
const { t } = useI18n()
const locale = ref('zh-CN')
function changeLocale(lang: string) {
locale.value = lang
}
// 监听 locale 变化,懒加载对应语言包
watch(locale, (val) => {
loadLocaleMessages(val)
})
</script>
vue
import.meta.glob 详解
import.meta.glob 是 Vite 提供的 API,用于动态导入匹配的文件:
// 返回值结构(eager: true 模式)
{
'../../locales/zh-CN.js': { default: { hello: '你好,我是 mock' } },
'../../locales/en.js': { default: { hello: 'Hello, I am mock' } },
}
// 返回值结构(eager: false,默认模式)
{
'../../locales/zh-CN.js': () => import('./locales/zh-CN.js'),
'../../locales/en.js': () => import('./locales/en.js'),
}
typescript
转换为目标格式的处理链:
import.meta.glob 返回原始对象
→ Object.entries 转为数组 [key, value] 对
→ map 提取语言标识 + 异步函数
→ Object.fromEntries 转回对象 { locale: loader }
text
VS Code i18n Ally 配置
在 .vscode/settings.json 中添加:
{
"i18n-ally.localesPaths": ["locales"],
"i18n-ally.enabledParsers": ["json", "js"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.keystyle": "flat"
}
json
配置后效果:
- 模板中
$t('hello')后面内联显示翻译内容 - 左侧状态栏显示翻译进度百分比
- 支持通过快捷键提取文案
JIT 编译(Just-In-Time Compilation)
从 9.3 版本起默认启用,主要解决两个场景:
| 场景 | 说明 |
|---|---|
| CSP 限制 | Service Worker 环境下的内容安全策略问题 |
| SSR 动态翻译 | 服务端返回的消息需要运行时编译 |
启用 JIT 后,翻译资源将生成 AST 对象而非预编译的 JS 代码。如果之前使用 @intlify/cli 做过预编译,启用 JIT 后需要重新编译资源文件。
总结
unplugin-vue-i18n是 vue-i18n 的正确打开方式,提供预编译和自动导入- 通过
import.meta.glob实现翻译文件的动态导入和懒加载 - 翻译文件按 IETF 标准命名(
zh-CN、en、ja等) - 懒加载机制:首次切换有延迟,后续使用缓存
- 配合 i18n Ally 插件提升开发体验
legacy: false+runtimeOnly: true禁止修改,确保使用 Composition API
↑